package com.github.lzm320a99981e.autocoding.domain.mybatis;

import com.github.lzm320a99981e.autocoding.domain.Dateable;
import com.github.lzm320a99981e.autocoding.domain.Entity;
import com.github.lzm320a99981e.autocoding.domain.Pageable;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

/**
 * 基础 Service
 *
 * @param <R>  Repository
 * @param <E>  com.github.lzm320a99981e.component.basicmodel.Entity
 * @param <ID> PrimaryKey
 */
@SuppressWarnings("rawtypes")
public class BaseService<R extends BaseRepository<E, ID>, E extends Entity, ID> {
    private static final int BATCH_SIZE = 500;

    @Autowired
    protected R repository;

    /**
     * 新增
     *
     * @param entity
     * @return
     */
    public int insert(E entity) {
        beforeInsert(entity);
        return repository.insert(entity);
    }

    /**
     * 新增，只新增非Null字段
     *
     * @param entity
     * @return
     */
    public int insertSelective(E entity) {
        beforeInsert(entity);
        return repository.insertSelective(entity);
    }

    /**
     * 主键删除
     *
     * @param id
     * @return
     */
    public int deleteByPrimaryKey(ID id) {
        return repository.deleteByPrimaryKey(id);
    }

    /**
     * 多主键删除
     *
     * @param ids
     * @return
     */
    public int deleteByPrimaryKeys(Collection<ID> ids) {
        return repository.deleteByPrimaryKeys(new ArrayList<>(ids));
    }

    /**
     * 主键更新
     *
     * @param entity
     * @return
     */
    public int updateByPrimaryKey(E entity) {
        beforeUpdate(entity);
        return repository.updateByPrimaryKey(entity);
    }

    /**
     * 主键更新，只更新非Null字段
     *
     * @param entity
     * @return
     */
    public int updateByPrimaryKeySelective(E entity) {
        beforeUpdate(entity);
        return repository.updateByPrimaryKeySelective(entity);
    }

    /**
     * 主键查询
     *
     * @param id
     * @return
     */
    public E selectByPrimaryKey(ID id) {
        return repository.selectByPrimaryKey(id);
    }

    /**
     * 多个主键查询
     *
     * @param ids
     * @return
     */
    public List<E> selectByPrimaryKeys(Collection<ID> ids) {
        return repository.selectByPrimaryKeys(new ArrayList<>(ids));
    }

    /**
     * 查询所有
     *
     * @return
     */
    public List<E> selectAll() {
        return repository.selectAll();
    }

    /**
     * 实体查询
     *
     * @param entity
     * @return
     */
    public List<E> selectByEntity(E entity) {
        return repository.selectByEntity(entity);
    }

    /**
     * 实体查询，只获取第一条记录
     *
     * @param entity
     * @return
     */
    public E selectOneByEntity(E entity) {
        List<E> entities = repository.selectByEntity(entity);
        return (Objects.isNull(entities) || entities.isEmpty()) ? null : entities.get(0);
    }

    /**
     * 条件查询
     *
     * @param conditions
     * @return
     */
    public List<E> selectByConditions(Map<String, Object> conditions) {
        return repository.selectByConditions(conditions);
    }

    /**
     * 条件查询，只获取第一条记录
     *
     * @param conditions
     * @return
     */
    public E selectOneByConditions(Map<String, Object> conditions) {
        List<E> entities = repository.selectByConditions(conditions);
        return (Objects.isNull(entities) || entities.isEmpty()) ? null : entities.get(0);
    }

    /**
     * 分页查询
     *
     * @param page
     * @return
     */
    public Pageable selectPage(Pageable page) {
        PageInfo pageInfo = PageHelper.offsetPage(page.getOffset(), page.getPageSize()).doSelectPageInfo(() -> repository.selectAll());
        page.setTotalRecords(pageInfo.getTotal());
        page.setContent(pageInfo.getList());
        return page;
    }

    /**
     * 分页 + 实体条件 查询
     *
     * @param page
     * @param entity
     * @return
     */
    public Pageable selectPage(Pageable page, final E entity) {
        PageInfo pageInfo = PageHelper.offsetPage(page.getOffset(), page.getPageSize()).doSelectPageInfo(() -> repository.selectByEntity(entity));
        page.setTotalRecords(pageInfo.getTotal());
        page.setContent(pageInfo.getList());
        return page;
    }

    /**
     * 分页 + 自定义条件 查询
     *
     * @param page
     * @param conditions
     * @return
     */
    public Pageable selectPage(Pageable page, final Map<String, Object> conditions) {
        PageInfo pageInfo = PageHelper.offsetPage(page.getOffset(), page.getPageSize()).doSelectPageInfo(() -> repository.selectByConditions(conditions));
        page.setTotalRecords(pageInfo.getTotal());
        page.setContent(pageInfo.getList());
        return page;
    }

    /**
     * 批量插入
     *
     * @param entities
     * @return
     */
    public int insertBatch(List<E> entities) {
        return insertBatch(entities, BATCH_SIZE);
    }

    /**
     * 批量插入
     *
     * @param entities
     * @param batchSize
     * @return
     */
    public int insertBatch(List<E> entities, int batchSize) {
        beforeInsert(entities);
        int size = entities.size();
        int batchTime = size / batchSize;
        int remaining = size % batchSize;
        int updateCount = 0;
        for (int i = 0; i < batchTime; i++) {
            updateCount += repository.insertBatch(entities.subList(i * batchSize, (i + 1) * batchSize));
        }
        if (remaining > 0) {
            updateCount += repository.insertBatch(entities.subList(size - remaining, size));
        }
        return updateCount;
    }

    /**
     * 批量更新
     *
     * @param entities
     * @return
     */
    public int updateBatch(List<E> entities) {
        return updateBatch(entities, BATCH_SIZE);
    }

    /**
     * 批量更新
     *
     * @param entities
     * @param batchSize
     * @return
     */
    public int updateBatch(List<E> entities, int batchSize) {
        beforeUpdate(entities);
        int size = entities.size();
        int batchTime = size / batchSize;
        int remaining = size % batchSize;
        int updateCount = 0;
        for (int i = 0; i < batchTime; i++) {
            updateCount += repository.updateBatch(entities.subList(i * batchSize, (i + 1) * batchSize));
        }
        if (remaining > 0) {
            updateCount += repository.updateBatch(entities.subList(size - remaining, size));
        }
        return updateCount;
    }

    /**
     * 统计所有
     *
     * @return
     */
    public Long count() {
        return repository.count();
    }

    /**
     * 最大值
     *
     * @param column
     * @return
     */
    public Object selectMax(String column) {
        return selectByFunction(Func.MAX, column);
    }

    /**
     * 最小值
     *
     * @param column
     * @return
     */
    public Object selectMin(String column) {
        return selectByFunction(Func.MIN, column);
    }

    /**
     * 平均值
     *
     * @param column
     * @return
     */
    public Object selectAvg(String column) {
        return selectByFunction(Func.AVG, column);
    }

    /**
     * 累计
     *
     * @param column
     * @return
     */
    public Object selectSum(String column) {
        return selectByFunction(Func.SUM, column);
    }

    private Object selectByFunction(Func func, String column) {
        Map<String, Object> result = repository.selectByFunction(func.name(), column);
        if (Objects.isNull(result) || result.isEmpty()) {
            return null;
        }
        return result.values().iterator().next();
    }

    /**
     * 插入前操作
     *
     * @param entity
     */
    private void beforeInsert(E entity) {
        Class<? extends Entity> entityClass = entity.getClass();
        if (Dateable.class.isAssignableFrom(entityClass) && Objects.isNull(((Dateable) entity).getExtCreatedDate())) {
            ((Dateable) entity).setExtCreatedDate(new Date());
        }
    }

    /**
     * 更新前操作
     *
     * @param entity
     */
    private void beforeUpdate(E entity) {
        Class<? extends Entity> entityClass = entity.getClass();
        if (Dateable.class.isAssignableFrom(entityClass) && Objects.isNull(((Dateable) entity).getExtLastModifiedDate())) {
            ((Dateable) entity).setExtLastModifiedDate(new Date());
        }
    }

    private void beforeInsert(List<E> entities) {
        entities.forEach(this::beforeInsert);
    }

    private void beforeUpdate(List<E> entities) {
        entities.forEach(this::beforeUpdate);
    }
}
